﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Argos.Collections;
using Argos.Data.Model;
using System.ComponentModel;
using System.Configuration;
using System.Workflow.Runtime;
using Argos.Tracking;
using System.Drawing;
using Argos.Hosting;
using Argos.WorkflowServices;

namespace Argos.Windows.Forms
{
	/// <summary>
	/// Creates a user interface that displays a list of <see cref="Target"/> items and allows
	/// the user to work with those targets.
	/// </summary>
	public partial class TargetList: UserControl
	{

		#region #ctor: public TargetList()

		/// <summary>
		/// Creates a new instance of the class.
		/// </summary>
		public TargetList()
		{
			InitializeComponent();

			this.HandleCreated += delegate(object sender, EventArgs e)
			{
				this.WindowHandleCreated = true;
			};

			int[] arr = Settings.Default.ColumnWidths;
			if (null != arr)
			{
				for (int i = 0; i < arr.Length; i++)
				{
					if (this.TargetGrid.Columns.Count > i)
					{
						this.TargetGrid.Columns[i].Width = arr[i];
					}
				}
			}
			this.TargetGrid.AutoSizeColumnsMode = Settings.Default.ColumnAutoSizeMode;
			this.autoSizeColumnsToolStripMenuItem.Checked = this.TargetGrid.AutoSizeColumnsMode == DataGridViewAutoSizeColumnsMode.AllCells;

			this.TargetGrid.ColumnWidthChanged += this.TargetGrid_ColumnWidthChanged;
		}

		#endregion



		private bool WindowHandleCreated = false;



		#region public event EventHandler<EventArgs> NewItem

		private EventHandler<EventArgs> NewItemHandler;

		/// <summary>
		/// Fired when the user wants to create a new item in the list.
		/// </summary>
		public event EventHandler<EventArgs> NewItem
		{
			add { this.NewItemHandler += value; }
			remove { this.NewItemHandler -= value; }
		}

		#endregion

		#region public event EventHandler<ItemEventArgs<Target>> TargetDeleted

		private EventHandler<ItemEventArgs<Target>> TargetDeletedHandler;

		/// <summary>
		/// Fired when the user deletes one or more targets in the list.
		/// </summary>
		/// <remarks>
		/// If the user deletes more than one target at a time, each deleted
		/// target will fire this event.
		/// </remarks>
		public event EventHandler<ItemEventArgs<Target>> TargetDeleted
		{
			add { this.TargetDeletedHandler += value; }
			remove { this.TargetDeletedHandler -= value; }
		}

		#endregion

		#region public event EventHandler<ItemEventArgs<Target>> TargetModified

		private EventHandler<ItemEventArgs<Target>> TargetModifiedHandler;

		/// <summary>
		/// Fired when a target has been modified in the list.
		/// </summary>
		public event EventHandler<ItemEventArgs<Target>> TargetModified
		{
			add { this.TargetModifiedHandler += value; }
			remove { this.TargetModifiedHandler -= value; }
		}

		#endregion
		
		#region public event EventHandler<ItemEventArgs<Target>> TargetOpened

		private EventHandler<ItemEventArgs<Target>> TargetOpenedHandler;

		/// <summary>
		/// Fired when the user opens a target in the list.
		/// </summary>
		public event EventHandler<ItemEventArgs<Target>> TargetOpened
		{
			add { this.TargetOpenedHandler += value; }
			remove { this.TargetOpenedHandler -= value; }
		}

		#endregion

		#region public event EventHandler<CancelEventArgs> TargetsDeleting

		private EventHandler<CancelEventArgs> TargetsDeletingHandler;

		/// <summary>
		/// Fired when the selected items are about to be deleted by the user.
		/// </summary>
		public event EventHandler<CancelEventArgs> TargetsDeleting
		{
			add { this.TargetsDeletingHandler += value; }
			remove { this.TargetsDeletingHandler -= value; }
		}

		#endregion

		#region public event EventHandler<EventArgs> WorkflowRuntimeChanged

		private EventHandler<EventArgs> WorkflowRuntimeChangedHandler;

		/// <summary>
		/// Fired when the <see cref="WorkflowRuntime"/> property
		/// has changed.
		/// </summary>
		public event EventHandler<EventArgs> WorkflowRuntimeChanged
		{
			add { this.WorkflowRuntimeChangedHandler += value; }
			remove { this.WorkflowRuntimeChangedHandler -= value; }
		}

		#endregion



		#region public IList<Target> Items { get; }

		private EventfulList<Target> _Items;

		[Browsable(false)]
		public IList<Target> Items
		{
			get
			{
				if (null == _Items)
				{
					_Items = new EventfulList<Target>();
					_Items.Cleared += this.Items_Cleared;
					_Items.ItemAdded += this.Items_ItemAdded;
					_Items.ItemSet += this.Items_ItemSet;
					_Items.ItemRemoved += this.Items_ItemRemoved;
				}
				return _Items;
			}
		}

		#endregion

		#region public MonitoringRuntime MonitoringRuntime { get; set; }

		private MonitoringRuntime _MonitoringRuntime;

		/// <summary>
		/// Sets or returns the monitoring runtime to use in the list.
		/// </summary>
		/// <remarks>
		/// This property is necessary in order for the list to track the
		/// states of each target displayed in the list.
		/// </remarks>
		public MonitoringRuntime MonitoringRuntime
		{
			get { return _MonitoringRuntime; }
			set
			{
				if (!object.Equals(this.MonitoringRuntime, value))
				{
					_MonitoringRuntime = value;

					if (null != _MonitoringRuntime)
					{
						_MonitoringRuntime.StateTrackingService.CurrentStateChanged += this.StateTrackingService_CurrentStateChanged;
						_MonitoringRuntime.TestExecutionService.TargetTesting += this.TestExecutionService_TargetTesting;
					}
				}
			}
		}

		#endregion
		
		#region public IList<Target> SelectedTargets { get; }

		/// <summary>
		/// Returns a list containing the currently selected targets
		/// in the list.
		/// </summary>
		/// <remarks>
		/// The items in this list cannot be modified.
		/// </remarks>
		public IList<Target> SelectedTargets
		{
			get
			{
				EventHandler<CancelEventArgs> handler1 = delegate(object sender, CancelEventArgs e)
				{
					e.Cancel = true;
					throw new InvalidOperationException("Cannot modify the members of the list.");
				};

				EventHandler<CancelItemEventArgs<Target>> handler2 = delegate(object sender, CancelItemEventArgs<Target> e)
				{
					e.Cancel = true;
					throw new InvalidOperationException("Cannot modify the members of the list.");
				};


				EventfulList<Target> list = new EventfulList<Target>();

				foreach (DataGridViewRow row in this.TargetGrid.SelectedRows)
				{
					Target t = row.Tag as Target;
					if (null != t)
					{
						list.Add(t);
					}
				}

				list.Clearing += handler1;
				list.ItemAdding += handler2;
				list.ItemRemoving += handler2;
				list.ItemSetting += handler2;

				return list;
			}
		}

		#endregion



		#region protected virtual void OnNewItem(EventArgs e)

		/// <summary>
		/// Fires the <see cref="NewItem"/> event.
		/// </summary>
		protected virtual void OnNewItem(EventArgs e)
		{
			if (null != this.NewItemHandler)
			{
				this.NewItemHandler.Invoke(this, e);
			}
		}

		#endregion
		
		#region protected virtual void OnTargetDeleted(ItemEventArgs<Target> e)

		/// <summary>
		/// Fires the <see cref="TargetDeleted"/> event.
		/// </summary>
		protected virtual void OnTargetDeleted(ItemEventArgs<Target> e)
		{
			if (null != this.TargetDeletedHandler)
			{
				this.TargetDeletedHandler.Invoke(this, e);
			}
		}

		#endregion

		#region protected virtual void OnTargetModified(ItemEventArgs<Target> e)

		/// <summary>
		/// Fires the <see cref="TargetModified"/> event.
		/// </summary>
		protected virtual void OnTargetModified(ItemEventArgs<Target> e)
		{
			if (null != this.TargetModifiedHandler)
			{
				this.TargetModifiedHandler.Invoke(this, e);
			}
		}

		#endregion
		
		#region protected virtual void OnTargetOpened(ItemEventArgs<Target> e)

		/// <summary>
		/// Fires the <see cref="TargetOpened"/> event.
		/// </summary>
		protected virtual void OnTargetOpened(ItemEventArgs<Target> e)
		{
			if (null != this.TargetOpenedHandler)
			{
				this.TargetOpenedHandler.Invoke(this, e);
			}
		}

		#endregion

		#region protected virtual void OnTargetsDeleting(CancelEventArgs e)

		/// <summary>
		/// Fires the <see cref="TargetsDeleting"/> event.
		/// </summary>
		protected virtual void OnTargetsDeleting(CancelEventArgs e)
		{
			if (null != this.TargetsDeletingHandler)
			{
				this.TargetsDeletingHandler.Invoke(this, e);
			}
		}
		
		#endregion

		#region protected virtual void OnWorkflowRuntimeChanged(EventArgs e)

		/// <summary>
		/// Fires the <see cref="WorkflowRuntimeChanged"/> event.
		/// </summary>
		protected virtual void OnWorkflowRuntimeChanged(EventArgs e)
		{
			if (null != this.WorkflowRuntimeChangedHandler)
			{
				this.WorkflowRuntimeChangedHandler.Invoke(this, e);
			}
		}

		#endregion



		#region private DataGridViewRow FindTargetRow(Guid targetId)

		/// <summary>
		/// Finds the row for the target with the given ID.
		/// </summary>
		/// <param name="targetId">The ID of the target whose row to find.</param>
		private DataGridViewRow FindTargetRow(Guid targetId)
		{
			DataGridViewRow[] rows = new DataGridViewRow[this.TargetGrid.Rows.Count];
			this.TargetGrid.Rows.CopyTo(rows, 0);
			DataGridViewRow row = rows.FirstOrDefault(r => r.Tag is Target && ((Target)r.Tag).Id == targetId);
			return row;
		}

		#endregion

		#region private DataGridViewRow FindWorkflowInstanceRow(Guid instanceId)

		/// <summary>
		/// Finds the row in the list that represents the target whose
		/// monitoring workflow instance id is specified in <paramref name="instanceId"/>.
		/// </summary>
		/// <param name="instanceId">The workflow instance id whose row to return.</param>
		private DataGridViewRow FindWorkflowInstanceRow(Guid instanceId)
		{
			if (null != this.MonitoringRuntime)
			{
				Guid? targetId = this.MonitoringRuntime.TargetInstanceTrackingService.GetTargetId(instanceId);
				if (targetId.HasValue)
				{
					return this.FindTargetRow(targetId.Value);
				}
			}
			return null;
		}

		#endregion

		#region private Image GetStateImage(string stateName)

		/// <summary>
		/// Returns the 12x12 pixel image that is associated with the given
		/// state name.
		/// </summary>
		/// <param name="stateName">The name of the state whose image to return.</param>
		private Image GetStateImage(string stateName)
		{
			Image img = null;

			switch (stateName)
			{
				case "Started":
					img = Properties.Resources.greenball01_12;
					break;

				case "Passed":
					img = Properties.Resources.greenball01_12;
					break;

				case "Testing":
					img = Properties.Resources.flash02_12;
					break;

				case "Failed":
					img = Properties.Resources.readball01_12;
					break;

				case "Alerted":
					img = Properties.Resources.readball02_12;
					break;

				case "Paused":
					img = Properties.Resources.yellowball01_12;
					break;

				case "Stopped":
					img = Properties.Resources.grayball01_12;
					break;

				case "Idle":
					img = Properties.Resources.greenball01_12;
					break;
			}

			return img;
		}

		#endregion
		
		#region private void SetDataGridRowValues(DataGridViewRow row, Target target)

		/// <summary>
		/// Sets the values for the given row from the given target.
		/// </summary>
		/// <param name="row">The row instance whose values to set.</param>
		/// <param name="target">The target whose data to use when setting the values for the given row.</param>
		private void SetDataGridRowValues(DataGridViewRow row, Target target)
		{
			if (null == row || null == target)
			{
				return;
			}

			Image img = null;

			if (target.Enabled && null != this.MonitoringRuntime)
			{
				Guid? instanceId = this.MonitoringRuntime.TargetInstanceTrackingService.GetWorkflowInstanceId(target.Id);
				if (instanceId.HasValue)
				{
					string stateName = this.MonitoringRuntime.StateTrackingService.GetCurrentStateName(instanceId.Value);
					img = this.GetStateImage(stateName);
				}
			}

			if (null == img)
			{
				img = Properties.Resources.grayball01_12;
			}

			this.SetDataGridRowValues(row, target, img);
		}

		#endregion

		#region private void SetDataGridRowValues(DataGridViewRow row, Target target, Image image)

		/// <summary>
		/// Sets the values for the given row from the given target and sets the
		/// image of that row to the given image.
		/// </summary>
		/// <param name="row">The row whose values to set.</param>
		/// <param name="target">The target whose values to set on the row.</param>
		/// <param name="image">The image for the row.</param>
		private void SetDataGridRowValues(DataGridViewRow row, Target target, Image image)
		{
			if (null == row || null == target)
			{
				return;
			}

			row.Cells[this.NameColumn.Name].Value = target.Name;
			row.Cells[this.NameColumn.Name].ToolTipText = target.Description;
			row.Cells[this.TypeColumn.Name].Value = null != target.MonitoringWorkflowType ? target.MonitoringWorkflowType.GetDisplayName(target.MonitoringWorkflowType.Name) : "";
			row.Cells[this.TypeColumn.Name].ToolTipText = null != target.MonitoringWorkflowType ? target.MonitoringWorkflowType.GetDescription() : "";
			row.Cells[this.TestColumn.Name].Value = null != target.Test.ActivityType ? target.Test.ActivityType.GetDisplayName(target.Test.ActivityType.Name) : "";
			row.Cells[this.TestColumn.Name].ToolTipText = null != target.Test.ActivityType ? target.Test.ActivityType.GetDescription() : "";
			row.Cells[this.TimeoutColumn.Name].Value = target.Test.ExecutionTimeout.TotalMilliseconds;
			row.Cells[this.IntervalColumn.Name].Value = (int)target.TestExecutionInterval.TotalSeconds;

			DataGridViewImageCell cell = (DataGridViewImageCell)row.Cells[this.TargetIconColumn.Name];
			Action<Image> setCellImage = delegate(Image img)
			{
				cell.Value = img;
			};
			if (this.WindowHandleCreated)
			{
				this.TargetGrid.Invoke(setCellImage, image);
			}

			row.Tag = target;
		}

		#endregion



		private void Items_Cleared(object sender, EventArgs e)
		{
			this.TargetGrid.Rows.Clear();
		}

		private void Items_ItemAdded(object sender, ItemEventArgs<Target> e)
		{
			int index = this.TargetGrid.Rows.Add();
			this.SetDataGridRowValues(this.TargetGrid.Rows[index], e.Item);
		}

		private void Items_ItemSet(object sender, ItemEventArgs<Target> e)
		{
			DataGridViewRow itemRow = null;
			foreach (DataGridViewRow row in this.TargetGrid.Rows)
			{
				Target t = row.Tag as Target;
				if (null != t)
				{
					if (Guid.Equals(t.Id, e.Item.Id))
					{
						itemRow = row;
						break;
					}
				}
			}

			if (null != itemRow)
			{
				this.SetDataGridRowValues(itemRow, e.Item);
			}

			this.OnTargetModified(new ItemEventArgs<Target>(e.Item));
		}

		private void Items_ItemRemoved(object sender, ItemEventArgs<Target> e)
		{
			foreach (DataGridViewRow row in this.TargetGrid.Rows)
			{
				Target t = row.Tag as Target;
				if (null != t && t.Id.Equals(e.Item.Id))
				{
					this.TargetGrid.Rows.Remove(row);
					break;
				}
			}
		}

		private void StateTrackingService_CurrentStateChanged(object sender, ItemEventArgs<Guid> e)
		{
			var row = this.FindWorkflowInstanceRow(e.Item);
			if (null != row)
			{
				this.SetDataGridRowValues(row, row.Tag as Target);
			}

		}

		private void TestExecutionService_TargetTesting(object sender, TargetTestingEventArgs e)
		{
			var row = this.FindTargetRow(e.TargetId);
			if (null != row)
			{
				Image img = this.GetStateImage("Testing");
				this.SetDataGridRowValues(row, row.Tag as Target, img);
			}
		}



		private void openToolStripMenuItem_Click(object sender, EventArgs e)
		{
			foreach (DataGridViewRow row in this.TargetGrid.SelectedRows)
			{
				Target t = row.Tag as Target;
				if (null != t)
				{
					this.OnTargetOpened(new ItemEventArgs<Target>(t));
				}
			}
		}

		private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
		{

			CancelEventArgs e2 = new CancelEventArgs(false);
			this.OnTargetsDeleting(e2);
			if (e2.Cancel)
			{
				return;
			}

			List<DataGridViewRow> remove = new List<DataGridViewRow>();

			foreach (DataGridViewRow row in this.TargetGrid.SelectedRows)
			{
				remove.Add(row);
			}

			foreach (DataGridViewRow row in remove)
			{
				Target t = row.Tag as Target;
				if (null != t)
				{
					this.Items.Remove(t);
					this.OnTargetDeleted(new ItemEventArgs<Target>(t));
				}
			}

		}

		private void newToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.OnNewItem(EventArgs.Empty);
		}

		private void TargetGrid_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
		{
			List<int> list = new List<int>();

			foreach (DataGridViewColumn col in this.TargetGrid.Columns)
			{
				list.Add(col.Width);
			}

			Settings.Default.ColumnWidths = new int[list.Count];
			list.CopyTo(Settings.Default.ColumnWidths);
			Settings.Default.Save();
		}

		private void TargetGrid_MouseDown(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Right)
			{
				DataGridView.HitTestInfo info = this.TargetGrid.HitTest(e.X, e.Y);
				if (info.RowIndex < 0 || !this.TargetGrid.Rows[info.RowIndex].Selected)
				{
					foreach (DataGridViewRow row in this.TargetGrid.SelectedRows)
					{
						row.Selected = false;
					}

					if (info.RowIndex >= 0)
					{
						this.TargetGrid.Rows[info.RowIndex].Selected = true;
					}
				}
			}
		}

		private void TargetGrid_MouseDoubleClick(object sender, MouseEventArgs e)
		{
			DataGridView.HitTestInfo info = this.TargetGrid.HitTest(e.X, e.Y);
			if (info.RowIndex >= 0 && info.ColumnIndex >= 0)
			{
				Target t = this.TargetGrid.Rows[info.RowIndex].Tag as Target;
				if (null != t)
				{
					this.OnTargetOpened(new ItemEventArgs<Target>(t));
				}
			}
		}

		private void TargetGrid_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.Modifiers == Keys.None)
			{
				if (e.KeyCode == Keys.Enter && this.TargetGrid.SelectedRows.Count > 0)
				{
					Target t = this.TargetGrid.SelectedRows[0].Tag as Target;
					if (null != t)
					{
						this.OnTargetOpened(new ItemEventArgs<Target>(t));
						e.Handled = true;
						e.SuppressKeyPress = true;
					}
				}
			}
		}

		private void GridContextMenu_Opening(object sender, CancelEventArgs e)
		{
			this.openToolStripMenuItem.Enabled = this.TargetGrid.SelectedRows.Count == 1;
			this.deleteToolStripMenuItem.Enabled = this.TargetGrid.SelectedRows.Count > 0;

			if (this.SelectedTargets.Count == 1)
			{
				this.enableToolStripMenuItem.Enabled = !this.SelectedTargets[0].Enabled;
				this.disableToolStripMenuItem.Enabled = !this.enableToolStripMenuItem.Enabled;
			}
			else if (this.SelectedTargets.Count > 1)
			{
				this.enableToolStripMenuItem.Enabled = true;
				this.disableToolStripMenuItem.Enabled = true;
			}
			else
			{
				this.enableToolStripMenuItem.Enabled = false;
				this.disableToolStripMenuItem.Enabled = false;
			}
		}

		private void enableToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (this.SelectedTargets.Count > 0)
			{
				foreach (Target t in this.SelectedTargets)
				{
					if (!t.Enabled)
					{
						t.Enabled = true;
						DataGridViewRow row = this.FindTargetRow(t.Id);
						this.SetDataGridRowValues(row, t);
						this.OnTargetModified(new ItemEventArgs<Target>(t));
					}
				}
			}
		}

		private void disableToolStripMenuItem_Click(object sender, EventArgs e)
		{
			if (this.SelectedTargets.Count > 0)
			{
				foreach (Target t in this.SelectedTargets)
				{
					if (t.Enabled)
					{
						t.Enabled = false;
						DataGridViewRow row = this.FindTargetRow(t.Id);
						this.SetDataGridRowValues(row, t);
						this.OnTargetModified(new ItemEventArgs<Target>(t));
					}
				}
			}
		}

		private void autoSizeColumnsToolStripMenuItem_Click(object sender, EventArgs e)
		{
			this.autoSizeColumnsToolStripMenuItem.Checked = !this.autoSizeColumnsToolStripMenuItem.Checked;

			if (this.autoSizeColumnsToolStripMenuItem.Checked)
			{
				Settings.Default.ColumnAutoSizeMode = DataGridViewAutoSizeColumnsMode.AllCells;
			}
			else
			{
				Settings.Default.ColumnAutoSizeMode = DataGridViewAutoSizeColumnsMode.None;
			}

			this.TargetGrid.AutoSizeColumnsMode = Settings.Default.ColumnAutoSizeMode;
			Settings.Default.Save();
		}



		private class Settings : ApplicationSettingsBase
		{

			public static Settings Default = (Settings)ApplicationSettingsBase.Synchronized(new Settings());

			[UserScopedSetting]
			public int[] ColumnWidths
			{
				get { return ((int[])(this["ColumnWidths"])); }
				set { this["ColumnWidths"] = value; }
			}

			[UserScopedSetting]
			[DefaultSettingValue("None")]
			public DataGridViewAutoSizeColumnsMode ColumnAutoSizeMode
			{
				get { return (DataGridViewAutoSizeColumnsMode)this["ColumnAutoSizeMode"]; }
				set { this["ColumnAutoSizeMode"] = value; }
			}

		}

	}
}
